using System;
using System.Security.Cryptography;
using System.Text;
//using PIM.Utilities.Cryptography.BlowFish;   

namespace PIM.Utilities.Cryptography
{
	/// <summary>
	/// Purpose : This class shall have methods that shall be 
	/// used to Encrypt/Decrypt any string.  
	/// </summary>
	public class QSecureEncrpytion
	{
		const string _STR_DEFAULT_KEY = "abcdefg";//done temporarily 

		# region Constructor


		/// <summary>
		/// The constructor will assign Algorithm type and call corresponding init() method. 
		/// </summary>
		/// 
		public QSecureEncrpytion(HashAlgo hAlgo)
		{
			Init(hAlgo);
		}

		/// <summary>
		/// The constructor will assign Algorithm type and call corresponding init() method. 
		/// </summary>
		/// 
		public QSecureEncrpytion(CryptoAlgo cAlgo)
		{
			Init(cAlgo);
		}
		# endregion
				
		# region Class Enumerator

		/// <summary>
		/// Purpose : This Enumeration will tell us the type of Symmetric Algorithms
		/// available to the application. ie, DES, RC2, Rijndael, TripleDES. All these algos are available in
		/// .net base class, System.Security.Cryptography. If you want to use 
		///	your own algorithm define it and use it. (like BlowFish)
		/// </summary>
		public enum CryptoAlgo : int 
		{ 
			DES, RC2, Rijndael, TripleDES , BlowFish
		} 

		/// <summary> 
		/// Enumeration of supported hash algorithms. 
		/// The Hash algorithm can be used to hash the Key before encyrption/decryption. 
		/// It can also be used to hash the password, and stored that hash value to your DB.
		/// </summary> 
		public enum HashAlgo : int 
		{ 
			MD5, SHA, SHA256, SHA384, SHA512, None 
		}

		# endregion
			
		# region Properties

		/// <summary>
		/// Private Member Variables of the class in Ascending Order.
		/// </summary>
		/// 
		
		private CryptoAlgo _cryptoUsed = CryptoAlgo.DES ;
		private HashAlgo _hashUsed = HashAlgo.None;
		private HashAlgorithmEngine	_HashAlgoEngine;
		private bool _bIsHash  ;
		private bool _bIsSymmetric  ;
		private string _strKey = null;				
		private SymmetricAlgorithmEngine _SymmetricAlgoEngine;
				

		# endregion

		# region Access Modifier

		/// <summary
		/// Purpose :   Public properties exposed from QSecureEncryption.
		/// </summary>
		/// 
	
		public bool IsHashAlgorithm
		{
			get
			{
				return _bIsHash;
			}
			set
			{
						
			}
		}
		public bool IsSymmetricAlgorithm
		{
			get
			{
				return _bIsSymmetric;
			}
			set
			{
						
			}
		}
		public string Key
		{
			get
			{
				return _strKey;
			}
			set 
			{
				_strKey = this.formatKey(value);
			}
		}
				
		# endregion

		# region Methods Exposed

		/// <summary>
		/// The Method would Initialize properties with respect to SymmetricAlgorithm.
		/// </summary>
		/// <param name="al">Returns true or false, ie. initialization success or fail respectively.</param>
		/// <returns></returns>
		public bool Init(CryptoAlgo cAlgo)
		{
			_cryptoUsed = cAlgo;
			_bIsSymmetric = true;
			_bIsHash = false;
			return Init();
		}

		/// <summary>
		/// The Method would Initialize properties with respect to HashAlogorithm.
		/// </summary>
		/// <param name="hAlgo"></param>
		/// <returns>Returns true or false, ie, initialization success or fail respectively.</returns>
		public bool Init(HashAlgo hAlgo)
		{
			_hashUsed = hAlgo;
			_bIsSymmetric = false;
			_bIsHash = true;
			return Init();
		}

		/// <summary>
		/// Encrypting a string. 
		/// </summary>
		/// <param name="strSource"></param>
		/// <returns>Return an encrypted string.</returns>
		public string EncryptString(string strSource)
		{
			string strResult = "";
			if(_bIsHash == true)
			{
				strResult = EncryptString(strSource,_hashUsed);
			}
			else if(_bIsSymmetric == true)
			{
				strResult = EncryptString(strSource,_cryptoUsed);
			}
			return strResult;
		}

		/// <summary>
		/// Overload of EncryptString, Encrypting the string strSource, with the given symmetric/crypto algo.
		/// </summary>
		/// <param name="strSource"></param>
		/// <param name="cAlgo"></param>
		/// <returns>Return an encrypted string.</returns>
		public string EncryptString(string strSource, CryptoAlgo cAlgo)
		{
			string strResult = "";
			if(_bIsSymmetric == false && _bIsHash == false)
				Init(cAlgo);
			if(_strKey == null || _strKey.Length == 0)
				strResult = EncryptString(strSource,_cryptoUsed,_STR_DEFAULT_KEY );
			else
				strResult = EncryptString(strSource,_cryptoUsed,_strKey);

			return strResult;
		}

		/// <summary>
		/// Overload of EncryptString, 
		/// Encrypting the string strSource, with the given symmetric/cryto algo and key.
		/// </summary>
		/// <param name="strSource"></param>
		/// <param name="cAlgo"></param>
		/// <param name="k"></param>
		/// <returns>Return an encrypted string.</returns>
		public string EncryptString(string strSource, CryptoAlgo cAlgo, string strKey)
		{
			string strResult = "";
			if(!strKey.Equals(_STR_DEFAULT_KEY) )
				_strKey = strKey;
			if(_bIsSymmetric == false && _bIsHash == false)
				Init(cAlgo);
			strResult = _SymmetricAlgoEngine.Encrypting(strSource,strKey);
			return strResult;
		}

		/// <summary>
		/// Overload of EncryptString,
		/// Hashing the string with the given hash algorithm.
		/// </summary>
		/// <param name="strSource"></param>
		/// <param name="hAlgo"></param>
		/// <returns></returns>
		public string EncryptString(string strSource, HashAlgo hAlgo)
		{
			string strResult = "";
			if(_bIsSymmetric == false && _bIsHash == false)
				Init(hAlgo);
			strResult = _HashAlgoEngine.Encoding(strSource);
			return strResult;
		}

		/// <summary>
		/// Decrypting a string.
		/// </summary>
		/// <param name="strSource">Returns a decrypted string.</param>
		/// <returns></returns>
		public string DecryptString(string strSource)
		{
			string strResult = "";
			if(_bIsSymmetric == false && _bIsHash == false)
				Init(_cryptoUsed);
			if(_bIsSymmetric == true)
			{
				strResult = DecryptString(strSource,_cryptoUsed);
			}
			return strResult;	
					
		}

		/// <summary>
		/// The Method would decrypt the string strSource with the given algo.
		/// </summary>
		/// <param name="strSource"></param>
		/// <param name="cAlgo"></param>
		/// <returns>Returns a decrypted string.</returns>
		public string DecryptString(string strSource, CryptoAlgo cAlgo)
		{
			string strResult = "";
			if(_bIsSymmetric == false && _bIsHash == false)
				Init(cAlgo);

			if(_strKey == null || _strKey.Length == 0)
				strResult = DecryptString(strSource,_cryptoUsed,_STR_DEFAULT_KEY );
			else
				strResult = DecryptString(strSource,_cryptoUsed,_strKey);
						
			return strResult;
		}

		/// <summary>
		/// The Method would decrypt the string strSource with the given algo and key.
		/// </summary>
		/// <param name="strSource"></param>
		/// <param name="cAlgo"></param>
		/// <param name="k"></param>
		/// <returns>Returns a decrypted string.</returns>
		public string DecryptString(string strSource, CryptoAlgo cAlgo, string strKey)
		{
			string strResult = "";
			if(!strKey.Equals(_strKey))
				_strKey = strKey;

			if(_bIsSymmetric == false && _bIsHash == false)
				Init(cAlgo);

			strResult = _SymmetricAlgoEngine.Decrypting(strSource,strKey);

            if (this._cryptoUsed == CryptoAlgo.BlowFish)
		        strResult = RemoveNonPrintableCharacters(strResult);

			return strResult;			
		}

		# endregion
        string RemoveNonPrintableCharacters(string s)
        {
            StringBuilder result = new StringBuilder();
            for (int i = 0; i < s.Length; i++)
            {
                char c = s[i];
                byte b = (byte)c;
                if (b >= 32)
                    result.Append(c);
            }
            return result.ToString();
        }
		
		# region Initialization

		/// <summary>
		/// Purpose: Initilalizing 
		/// </summary>
		/// 
		private bool Init()
		{
			// If none of the Algo is specified then return false, ie., initialization Fails.
			if(_bIsSymmetric == false && _bIsHash == false )
				return false;

			_SymmetricAlgoEngine = null;
			_HashAlgoEngine = null;
			if(_bIsSymmetric == true)
			{
				switch(_cryptoUsed)
				{
					case CryptoAlgo.DES :
						_SymmetricAlgoEngine = new SymmetricAlgorithmEngine(CryptoAlgo.DES);
						break;	
					case CryptoAlgo.RC2 :
						_SymmetricAlgoEngine = new SymmetricAlgorithmEngine(CryptoAlgo.RC2);
						break;
					case CryptoAlgo.Rijndael :
						_SymmetricAlgoEngine = new SymmetricAlgorithmEngine(CryptoAlgo.Rijndael);
						break;
					case CryptoAlgo.TripleDES :
						_SymmetricAlgoEngine = new SymmetricAlgorithmEngine(CryptoAlgo.TripleDES);
						break;
					case CryptoAlgo.BlowFish:
						_SymmetricAlgoEngine = new SymmetricAlgorithmEngine(CryptoAlgo.BlowFish);
						break;
					default :
						return false;				
				}
			}
			if(_bIsHash == true)
			{
				switch(_hashUsed)
				{
					case HashAlgo.MD5 :
						_HashAlgoEngine = new HashAlgorithmEngine(HashAlgo.MD5);
						break;
					case HashAlgo.SHA :
						_HashAlgoEngine = new HashAlgorithmEngine(HashAlgo.SHA);
						break;
					case HashAlgo.SHA256 :
						_HashAlgoEngine = new HashAlgorithmEngine(HashAlgo.SHA256);
						break;
					case HashAlgo.SHA384 :
						_HashAlgoEngine = new HashAlgorithmEngine(HashAlgo.SHA384);
						break;
					case HashAlgo.SHA512 :
						_HashAlgoEngine = new HashAlgorithmEngine(HashAlgo.SHA512);
						break;
					default :
						return false;
				}
			}
			return true;
		}

		# endregion

		# region Validation/Formatting

		/// <summary>
		/// Purpose : Formatting the Key called from the property Key is specifying the value to _strKey
		/// </summary>
		/// 
		private string formatKey(string strKey)
		{
			if(strKey == null || strKey.Length == 0) 
				return null;
			return strKey.Trim();
		}

		#endregion 

		# region Hash Algorithm Engine


		/// <summary>
		/// Purpose : The Class will encode the string source, 
		/// </summary>
		/// 
		private class HashAlgorithmEngine
		{
			// Private Property of the HashAlgorithEngine.
			private HashAlgorithm encodeMethod ;

			# region Constructor 

			public HashAlgorithmEngine(HashAlgorithm serviceProvider)
			{
				encodeMethod = serviceProvider ;
			}

			public HashAlgorithmEngine (HashAlgo iSelect)
			{
				switch (iSelect )
				{
					case HashAlgo.SHA:
						encodeMethod = new SHA1CryptoServiceProvider();
						break;
					case HashAlgo.SHA256:
						encodeMethod = new SHA256Managed();
						break;
					case HashAlgo.SHA384:
						encodeMethod = new SHA384Managed();
						break;
					case HashAlgo.SHA512:
						encodeMethod = new SHA512Managed();
						break;
					case HashAlgo.MD5:
						encodeMethod = new MD5CryptoServiceProvider();
						break;
				}
			}

			# endregion

			#region Method

			/// <summary>
			/// Purpose: The public Method would return the Base64 hash of the string passed to it. 
			/// </summary>
			/// <param name="strSource">Its source string passed to the method.</param>
			/// <returns></returns>
			public string Encoding(string strSource)
			{
				//byte[] bytIn = System.Text.ASCIIEncoding.ASCII.GetBytes (strSource);
				byte[] bytIn = System.Text.UTF8Encoding.UTF8.GetBytes (strSource);
				byte[] bytOut = encodeMethod.ComputeHash (bytIn );

				// Convert into base64 so that strResult can be used in XML
				return System.Convert.ToBase64String(bytOut,0,bytOut.Length );
			}
			# endregion
		}

		# endregion

		#region Symmetric Algorithm Engine

		/// <summary>
		/// Purpose : The Class will Encrypt and Decrypt the string source
		/// <summary>
		private class SymmetricAlgorithmEngine
		{
					
			// Private properties.
			private SymmetricAlgorithm encodeMethod;

			/// <summary>
			/// Constructor, Take Symmetric Algorithm as parameter.
			/// </summary>
			/// <param name="serviceProvider"></param>
			public SymmetricAlgorithmEngine(SymmetricAlgorithm serviceProvider)
			{
				encodeMethod = serviceProvider;
			}

			/// <summary>
			/// Constructor, Taking cryptoalgo enum as the parameter to 
			/// Initialize the Symmetric Algorithm to be used for encryption and 
			/// decryption.
			/// </summary>
			/// <param name="iSelect"></param>
			public SymmetricAlgorithmEngine(CryptoAlgo iSelect)
			{
				switch(iSelect )
				{
					case CryptoAlgo .DES:
						encodeMethod = new DESCryptoServiceProvider ();
						break;
					case CryptoAlgo .RC2:
						encodeMethod = new RC2CryptoServiceProvider();
						break;
					case CryptoAlgo .Rijndael :
						encodeMethod = new RijndaelManaged();
						break;
					case CryptoAlgo .TripleDES :
						encodeMethod = new TripleDESCryptoServiceProvider ();
						break;
                    //case CryptoAlgo.BlowFish:
                    //    encodeMethod = new BlowfishAlgorithm();
                    //    encodeMethod.Mode = CipherMode.CBC;
                    //    encodeMethod.KeySize = 40;
                    //    encodeMethod.GenerateKey();
                    //    encodeMethod.GenerateIV();
                    //    break;
				}
			}

			/// <summary>
			/// Adding information to the key, to be used for encryption and decryption.
			/// </summary>
			/// <param name="Key"></param>
			/// <returns>Will return a Byte array of key. </returns>
			private byte[] getValidKey(string strKey)
			{
				string strTemp;
				if(encodeMethod.LegalKeySizes .Length > 0)
				{
					int nLessSize = 0;
					int nMoreSize = encodeMethod.LegalKeySizes[0].MinSize;

					//Key Sizes are in bits.
					while(  strKey .Length * 8 > nMoreSize && 
							encodeMethod .LegalKeySizes[0].SkipSize > 0 &&
							nMoreSize < encodeMethod .LegalKeySizes[0].MaxSize )
					{
						nLessSize = nMoreSize ;
						nMoreSize += encodeMethod.LegalKeySizes[0].SkipSize ;
					}

					if(strKey.Length * 8 > nMoreSize)
						strTemp = strKey.Substring(0, (nMoreSize / 8));
					else
						strTemp = strKey.PadRight(nMoreSize / 8, ' ');
				}
				else
				{
					strTemp = strKey ;
				}
				// Convert the secret strKey to byte array[]
				//return System.Text.ASCIIEncoding.ASCII.GetBytes(strTemp );
				return System.Text.UTF8Encoding.UTF8.GetBytes(strTemp );
			}

			/// <summary>
			/// Adding more information to the Initialization Vector. 
			/// </summary>
			/// <param name="strInitVector"></param>
			/// <param name="nValidLength"></param>
			/// <returns>Returns a byte array of IV</returns>
			private byte[] getValidIV(string strInitVector, int nValidLength)
			{
				if(strInitVector.Length > nValidLength)
				{
					//return System.Text.ASCIIEncoding.ASCII.GetBytes(strInitVector.Substring(0, nValidLength ));
					return System.Text.UTF8Encoding.UTF8.GetBytes(strInitVector.Substring(0, nValidLength ));
				}
				else
				{
					//return System.Text.ASCIIEncoding.ASCII.GetBytes(strInitVector.PadRight(nValidLength, ' '));
					return System.Text.UTF8Encoding.UTF8.GetBytes(strInitVector.PadRight(nValidLength, ' '));
				}
			}

			/// <summary>
			/// Logic of encryption is written in here. The method would take the string to encrypt 
			/// and the key against which use want to encrypt. The default key is "abcdefg"
			/// </summary>
			/// <param name="strSource">The string to encrypt.</param>
			/// <param name="key">The key against which we want to encrypt.</param>
			/// <returns>Return an encrypted string in Base64 Format.</returns>
			public string Encrypting(string strSource, string strKey)
			{
				// If either source or strKey is null then return NULL.
				if(strSource == null || strKey == null || strSource.Length == 0 || strKey.Length == 0)
					return null;

				// If Encode Method is not specified return NULL or add some Code to handle 
				// this situation.
				if(encodeMethod == null) 
				{
					// Write the code how to handle this situation.
					return "No Encoding Algorithm Detected.";
				}

				long lLength;
				int nRead, nReadTotal;
				byte[] buf = new byte[3];
				byte[] srcData;
				byte[] encData;
				System.IO.MemoryStream msIn;
				System.IO.MemoryStream msOut;
				CryptoStream encStream;

				//srcData = System.Text.ASCIIEncoding.ASCII.GetBytes(strSource );
				srcData = System.Text.UTF8Encoding.UTF8.GetBytes(strSource );

				msIn = new System.IO.MemoryStream();
				msIn .Write(srcData,0,srcData.Length );
				msIn.Position = 0;
				msOut = new System.IO.MemoryStream();

				encodeMethod.Key  = getValidKey(strKey );
				encodeMethod.IV = getValidIV(strKey, encodeMethod.IV.Length );

				encStream = new CryptoStream(msOut ,encodeMethod.CreateEncryptor(),CryptoStreamMode.Write);

				lLength = msIn.Length;
				nReadTotal = 0;

				while(nReadTotal < lLength)
				{
					nRead = msIn.Read(buf, 0, buf.Length);
					encStream.Write(buf,0,nRead );
					nReadTotal += nRead ;
				}
				encStream .Close();

				encData = msOut.ToArray();

				return Convert.ToBase64String(encData);
			}

			/// <summary>
			/// Logic of Decryption is written in here.
			/// </summary>
			/// <param name="strSource"></param>
			/// <param name="strKey"></param>
			/// <returns>Returns a decrypted string.</returns>
			public string Decrypting(string strSource, string strKey)
			{
				if(strSource == null || strKey == null || strSource.Length == 0 || strKey.Length == 0)
					return null;

				if(encodeMethod == null) 
				{
					// Write the code how to handle this situation.
					return "No Encoding Algorithm Detected.";
				}

				long lLength;
				int nRead, nReadTotal;
				byte[] buf = new byte[3];
				byte[] decData;
				byte[] encData;
				System.IO.MemoryStream msIn;
				System.IO.MemoryStream msOut;
				CryptoStream decStream;

				encData = System.Convert.FromBase64String(strSource);

				msIn = new System.IO.MemoryStream(encData );
				msOut = new System.IO.MemoryStream();

				encodeMethod.Key = getValidKey(strKey );
				encodeMethod.IV = getValidIV(strKey, encodeMethod.IV.Length );

				decStream = new CryptoStream(msIn, encodeMethod.CreateDecryptor(), CryptoStreamMode .Read );

				lLength = msIn.Length ;
				nReadTotal = 0;

				while(nReadTotal < lLength)
				{
					nRead = decStream.Read(buf,0,buf.Length );
					if(0 == nRead )
						break;
					msOut.Write(buf,0,nRead);
					nReadTotal += nRead;
				}

				decStream.Close();

				decData = msOut.ToArray();

				//System.Text.ASCIIEncoding ascEnc = new System.Text.ASCIIEncoding ();
				System.Text.UTF8Encoding ascEnc = new System.Text.UTF8Encoding ();
				return ascEnc.GetString(decData);
			}
		}

		#endregion

	}
}

